/*
 * PlanBlock.java
 * 
 * Copyright 2004 Christoph Csallner and Yannis Smaragdakis.
 */
package edu.gatech.cc.cnc.plans.stmt;

import static edu.gatech.cc.jcrasher.Assertions.notNull;

import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;
import java.util.List;

import edu.gatech.cc.jcrasher.plans.expr.Expression;
import edu.gatech.cc.jcrasher.plans.expr.Variable;
import edu.gatech.cc.jcrasher.plans.stmt.Block;
import edu.gatech.cc.jcrasher.plans.stmt.BlockStatement;
import edu.gatech.cc.jcrasher.plans.stmt.LocalVariableDeclarationStatement;

/**
 * Represents a sequence of statements that is contained in a block.
 * We mostly use it to create some values and
 * call a method on them.
 * This sequence does not contain opening or closing brackets.
 * It is therefore not a block or lexical scoping construct.
 * Nested elements are prefixes of this statement sequence
 * and not lexically nested.
 * <p>
 * Example:
 * <pre>
 *   P1 p1 = new P1(1);
 *   P2 ps = -3
 *   V v = new V();
 *   for (int i=0; i<2; i++) {
 *     Field.set(v, p1);
 *   }
 *   </pre>
 * 
 * @author csallner@gatech.edu (Christoph Csallner)
 */
public abstract class AbstractBlockStatementSequence<T> implements BlockStatementSequence<T> {
	
  protected final List<BlockStatement<?>> statements = new LinkedList<BlockStatement<?>>();
  
	protected Variable<T> name;
	protected Block<?> enclosingBlock;  //local context	
	protected Variable<?>[] localNames = new Variable[0];  //local names introduced in stmts.
	
	/**
   * Process initial plans 
	 */
	protected void init(BlockStatement<?>[] plans) {
    notNull(plans);
    
		localNames = new Variable[plans.length];
		
		for (int i=0; i<plans.length; i++) {
      
      /* TODO: Ugly hack. */
      
			if (plans[i] instanceof BlockStatementSequence) { //TODO: single statement missing?
				localNames[i] = ((BlockStatementSequence)plans[i]).getVariable();  //name of created value.
				statements.addAll(((BlockStatementSequence)plans[i]).getBlockStmts());
        continue;
			}      

      if (plans[i] instanceof LocalVariableDeclarationStatement) {
        localNames[i] = ((LocalVariableDeclarationStatement)plans[i]).getVariable();
        statements.add((LocalVariableDeclarationStatement)plans[i]);
        continue;
      }
      
      throw new IllegalArgumentException("not yet supported");
      
//			if (plans[i] instanceof Variable) {
//				localNames[i] = (Variable) plans[i]; //no need to create new name.
//        continue;
//			}
        
        
//			/* create new name. */
//      Expression<?> expression = (Expression<?>) plans[i];
//			localNames[i] = enclosingBlock.getNextID(expression.getReturnType());
//			statements.add(new LocalVariableDeclarationStatement(localNames[i], expression));
		}
	}
	
  
  /**
   * Constructor
   * 
   * @param name is the local name of the value to be created.
   */
  protected AbstractBlockStatementSequence(Block<?> enclosingBlock) 
  {
    this.enclosingBlock = notNull(enclosingBlock);
  }
  
	
	/**
	 * Constructor
	 * 
	 * @param name is the local name of the value to be created.
	 */
	protected AbstractBlockStatementSequence(
      Block<?> enclosingBlock, 
      Variable<T> name)
  {
    this(enclosingBlock);
		this.name = notNull(name);
	}
	
	/**
	 * Constructor
	 * 
	 * @param name is the local name of the value to be created.
	 */
	protected AbstractBlockStatementSequence(
			Block<?> enclosingBlock, 
			Variable<T> name, 
      BlockStatement<?>[] plans) 
	{
		this(enclosingBlock, name);
				
		init(plans);
	}
  
		
	/**
   * Appends the given statement sequence to this statement sequence.
   * 
	 * @return name of added plan, which is not added to internal list, though.
	 */
	public <V> Variable<? extends V> addPlan(BlockStatementSequence<V> plan) {			
		//			Variable localNamesNew = new Variable[localNames.length+1];
		//			System.arraycopy(localNames, 0, localNamesNew, 0, localNames.length-1);
		//			localNames = localNamesNew;			

	  statements.addAll(plan.getBlockStmts());
		return plan.getVariable();
	}
  
  /**
   * Appends the given assignment statement.
   * 
   * @return name of added plan, which is not added to internal list, though.
   */
  public <V> Variable<? extends V> addPlan(LocalVariableDeclarationStatement<V> plan) {      
    statements.add(plan);
    return plan.getVariable();
  }
  
  /**
   * Appends the following assignment statement to this sequence of assignments.
   * newName = plan. 
   * @return name of added plan, which is not added to internal list, though.
   */
  public <V> Variable<V> addPlan(Expression<V> plan) {      
    Variable<V> newName = enclosingBlock.getNextID(plan.getReturnType());
    statements.add(new LocalVariableDeclarationStatement<V>(newName, plan));
    return newName;     
  }  
	
  
	public Variable<T> getVariable() {
		if (name==null)
      throw new IllegalStateException("planned for void method?");
    
		return name;
	}
  
	public Class<? extends T> getReturnType() {
		if (name==null)
      throw new IllegalStateException("planned for void method?");
    
		return name.getReturnType();
	}
  
	public List<BlockStatement<?>> getBlockStmts() {
		return statements;
	}
	

  public T execute() throws InstantiationException,
      IllegalAccessException, InvocationTargetException {

    Object result = null;
    for (BlockStatement<?> statement: statements)
      result = statement.execute(); //TODO: Last one has to return T.
    
    return (T) result;  //TODO: Make sure that this is always true.
  }


  public String text() {
		StringBuffer sb = new StringBuffer();
		for (BlockStatement<?> statement: statements) {
			sb.append(statement.text()+"\n");
		}
		return sb.toString();
	}
	
	@Override
	public String toString() {
		return text();
	}
}
